#!/usr/bin/python2.7

# Copyright (c) 2011 CDN-Tool authors. All rights reserved.
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file.

import sys
import json
from socket import gethostname
from httplib import HTTPConnection
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from optparse import OptionParser
from SocketServer import ThreadingMixIn

from cdn_lib import *

class CDNHubRequestHandler(LoggingMixIn, BaseHTTPRequestHandler):

  logger = LoggingMixIn.log_init("CDNHubRequestHandler")

  def do_GET(self):
    """Handles an incoming request from a server.

    Args: two args, returned by socket.accept()
      conn - connection socket
      addr - (ip_address, port) of remote socket

    Returns nothing.
    """
    path, remote_host = self.path.split("?", 1)
    if path == "/add":    # Add remote_dict to hub's dict.
      # update this
      remote_dict = {remote_host: {"1": None}}
      self.send_response(200)
      self.end_headers()
      self.wfile.write(json.dumps(self.server.servers))
      self.server.update_dict(remote_dict)
    elif path == "/remove":  # Delete source server's file dict from hub's dict
      if remote_host in self.server.servers:
        self.server.update_dict(remote_host)
      else:
        self.log_warning(remote_host +
                       " tried to remove himself twice from the list.")
      self.send_response(200)
      self.end_headers()
    else:
      self.log_error("Invalid request: '%s'", self.path)
      self.send_error(404)


class CDNHub(ThreadingMixIn, LoggingMixIn, HTTPServer):

  servers = {}    # Dict of available servers
  _address = None
  logger = LoggingMixIn.log_init("CDNHub")

  def __init__(self, port):
    HTTPServer.__init__(self, (gethostname(), port), CDNHubRequestHandler)
    self._address = (self.socket.getsockname()[0] + ":" +
                     str(self.socket.getsockname()[1]))
    self.log_info("Hub bound to %s.", self._address)

  def update_dict(self, update):
    if isinstance(update, dict):
      pass
    elif isinstance(update, str):
      del self.servers[update]
    else:
      self.log_error("Update arg is of bad type!")
      return
    for server in self.servers.keys():
      t = threading.Thread(target=self._send_update,
                           args=(server, update))
      t.start()
    if isinstance(update, dict):
      self.servers.update(update)
    self.log_info("Successfully merged file dicts.")
    self.log_debug("Servers: %s", self.servers)

  def _send_update(self, server, update):
    """Sends update message to the server

    Sends a message saying that there's one more server online
    ("+" + dict_of_files) or a message saying that one of the servers has gone
    offline ("-" + server_address).

    Args:
      server: server address as a string (e.g. "0.0.0.0:3000")
      update: either a dict {
        server_address_as_a_string: {
          "file_1_id": None,
          ...,
          "file_n_id: None
          }
      }
      or a string representing server address. If it's a dict, the function will
      send a message saying that there's one more server. If it's just a string,
      it will send a message saying that one server has gone offline.

    Returns nothing.

    Asserts that update is either a dict or a string.
    """
    connection = HTTPConnection(server)
    if isinstance(update, str):
      # update this
      connection.request("HEAD", "/cdn-hub/remove?" + update)
    elif isinstance(update, dict):
      # update this
      connection.request("HEAD", "/cdn-hub/add", json.dumps(update))
    else:
      self.log_error("Update arg is of bad type!")
      return
    self.log_info("Sending request to %s", server)
    response = connection.getresponse()
    if response.status == 200:
      self.log_info("Succesfully sent update to %s.", server)
    else:
      self.log_warning("Couldn't send update to %s. "
                   "Got response status %s", server, response.status)


if __name__ == "__main__":
  parser = OptionParser()
  parser.add_option("--loglevel", dest="loglevel", default="DEBUG")
  parser.add_option("--logfile", dest="logfile")
  (options, args) = parser.parse_args()

  set_log_handlers(options.loglevel, options.logfile,
      "CDNHub", "CDNHubRequestHandler")
      
  hub = CDNHub(int(HUB_ADDRESS.split(":")[1]))
  try:
    hub.serve_forever()
  except KeyboardInterrupt:
    print "KeyboardInterrupt exception received. Program terminating."
    hub.shutdown()
    sys.exit()
